<?php

// +----------------------------------------------------------------------
// | 当前模块使用的是 Library for ThinkAdmin 版权归其所有
// +----------------------------------------------------------------------
// | 版权所有 2014~2022 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
// +----------------------------------------------------------------------
// | 官方网站: https://gitee.com/zoujingli/ThinkLibrary
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// +----------------------------------------------------------------------

declare (strict_types=1);


namespace quick\admin\library\storage;


use quick\admin\form\Form;
use quick\admin\library\tools\HttpTools;

/**
 * 七牛云存储支持
 * Class QiniuStorage
 * @package think\admin\storage
 */
class QiniuStorage extends Storage
{

    private $bucket;
    private $accessKey;
    private $secretKey;

    /**
     *
     */
    protected function initialize()
    {
        // 读取配置文件
        $this->bucket = $this->config['bucket'];
        $this->accessKey = $this->config['access_key'];
        $this->secretKey = $this->config['secret_key'];
        // 计算链接前缀
        $type = strtolower($this->config['protocol']);
        $domain = strtolower($this->config['domain']);
        if ($type === 'auto') {
            $this->domian = "//{$domain}";
        } elseif (in_array($type, ['http', 'https'])) {
            $this->domian = "{$type}://{$domain}";
        } else throw new \Exception('未配置七牛云URL域名哦');
    }


    /**
     * @param string $name
     * @param string $file
     * @param bool $safe
     * @param string|null $attname
     * @return array
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function save(string $name, string $file, bool $safe = false, ?string $attname = null): array
    {
        $token = $this->buildUploadToken($name, 3600, $attname);
        $data = ['key' => $name, 'token' => $token, 'fileName' => $name];
        $file = ['field' => "file", 'name' => $name, 'content' => $file];
        $result = HttpTools::submit($this->upload(), $data, $file, [], 'POST', false);
        return json_decode($result, true);
    }


    /**
     * 根据文件名读取文件内容
     * @param string $name 文件名称
     * @param boolean $safe 安全模式
     * @return string
     */
    public function get(string $name, bool $safe = false): string
    {
        $url = $this->url($name, $safe) . "?e=" . time();
        $token = "{$this->accessKey}:{$this->safeBase64(hash_hmac('sha1', $url, $this->secretKey, true))}";
        return static::curlGet("{$url}&token={$token}");
    }

    /**
     * 删除存储的文件
     * @param string $name 文件名称
     * @param boolean $safe 安全模式
     * @return boolean
     */
    public function del(string $name, bool $safe = false): bool
    {
        [$EncodedEntryURI, $AccessToken] = $this->getAccessToken($name, 'delete');
        $data = json_decode(HttpTools::post("http://rs.qiniu.com/delete/{$EncodedEntryURI}", [], [
            'headers' => ["Authorization:QBox {$AccessToken}"],
        ]), true);
        return empty($data['error']);
    }

    /**
     * 检查文件是否已经存在
     * @param string $name 文件名称
     * @param boolean $safe 安全模式
     * @return boolean
     */
    public function has(string $name, bool $safe = false): bool
    {
        return is_array($this->info($name, $safe));
    }

    /**
     * 获取文件当前URL地址
     * @param string $name 文件名称
     * @param boolean $safe 安全模式
     * @param null|string $attname 下载名称
     * @return string
     */
    public function url(string $name, bool $safe = false, ?string $attname = null): string
    {
        return "{$this->domian}/{$this->delSuffix($name)}{$this->getSuffix($attname,$name)}";
    }

    /**
     * 获取文件存储路径
     * @param string $name 文件名称
     * @param boolean $safe 安全模式
     * @return string
     */
    public function savePath(string $name, bool $safe = false): string
    {
        return $this->url($name, $safe);
    }

    /**
     * 获取文件存储信息
     * @param string $name 文件名称
     * @param boolean $safe 安全模式
     * @param null|string $attname 下载名称
     * @return array
     */
    public function info(string $name, bool $safe = false, ?string $attname = null): array
    {
        [$entry, $token] = $this->getAccessToken($name);
        $data = json_decode(HttpTools::get("http://rs.qiniu.com/stat/{$entry}", [], ['headers' => ["Authorization: QBox {$token}"]]), true);
        return isset($data['md5']) ? ['file' => $name, 'url' => $this->url($name, $safe, $attname), 'key' => $name] : [];
    }

    /**
     * @return string
     */
    public function upload(): string
    {
        $protocol = $this->app->request->isSsl() ? 'https' : 'http';
        switch (sysConfig('storage.qiniu_region')) {
            case '华东':
                return "{$protocol}://up.qiniup.com";
            case '华北':
                return "{$protocol}://up-z1.qiniup.com";
            case '华南':
                return "{$protocol}://up-z2.qiniup.com";
            case '北美':
                return "{$protocol}://up-na0.qiniup.com";
            case '东南亚':
                return "{$protocol}://up-as0.qiniup.com";
            default:
                throw new \Exception('未配置七牛云空间区域哦');
        }
    }

    /**
     * 获取文件上传令牌
     * @param null|string $name 文件名称
     * @param integer $expires 有效时间
     * @param null|string $attname 下载名称
     * @return string
     */
    public function buildUploadToken(?string $name = null, int $expires = 3600, ?string $attname = null): string
    {
        $policy = $this->safeBase64(json_encode([
            "deadline"   => time() + $expires, "scope" => is_null($name) ? $this->bucket : "{$this->bucket}:{$name}",
            'returnBody' => json_encode(['uploaded' => true, 'filename' => '$(key)', 'url' => "{$this->domian}/$(key){$this->getSuffix($attname,$name)}", 'key' => $name, 'file' => $name], JSON_UNESCAPED_UNICODE),
        ]));
        return "{$this->accessKey}:{$this->safeBase64(hash_hmac('sha1', $policy, $this->secretKey, true))}:{$policy}";
    }

    /**
     * URL安全的Base64编码
     * @param string $content
     * @return string
     */
    private function safeBase64(string $content): string
    {
        return str_replace(['+', '/'], ['-', '_'], base64_encode($content));
    }

    /**
     * 获取对象管理凭证
     * @param string $name 文件名称
     * @param string $type 操作类型
     * @return array
     */
    private function getAccessToken(string $name, string $type = 'stat'): array
    {
        $entry = $this->safeBase64("{$this->bucket}:{$name}");
        $sign = hash_hmac('sha1', "/{$type}/{$entry}\n", $this->secretKey, true);
        return [$entry, "{$this->accessKey}:{$this->safeBase64($sign)}"];
    }

    /**
     * 七牛云对象存储区域
     * @return array
     */
    public static function region(): array
    {
        return [
            'up.qiniup.com'     => '华东',
            'up-z1.qiniup.com'  => '华北',
            'up-z2.qiniup.com'  => '华南',
            'up-na0.qiniup.com' => '北美',
            'up-as0.qiniup.com' => '东南亚',
        ];
    }

    public function configForm():Form
    {
        $form = Form::make();
        $form->radio('name_type','命名方式')->options([
            'hash' => '文件哈希值',
            'date' => '日期加随机',
        ])->default('hash')->required();
        $form->radio('link_type','链接类型')->options([
            'hash' => '简洁链接',
            'date' => '完整链接',
        ])->help('类型为“简洁链接”时链接将只返回 hash 地址，而“完整链接”将携带参数保留文件名，图片压缩功能云平台会单独收费。');
        $form->radio('protocol','访问协议')->options([
            'follow' => 'FOLLOW',
            'http' => 'HTTP',
            'https' => 'HTTPS',
            'path' => 'PATH',
            'auto' => 'AUTO',
        ])->required()->help('本地存储访问协议，其中 HTTPS 需要配置证书才能使用（ FOLLOW 跟随系统，PATH 文件路径，AUTO 相对协议 ）');
        $form->text('allow_exts','允许类型')
            ->required()->help('设置系统允许上传文件的后缀，多个以英文逗号隔开如：png,jpg,rar,doc，未设置允许上传的后缀');
        $form->text('domain','访问域名')
            ->help('填写上传后的访问域名（不指定时根据当前访问地址自动计算），如：static.quickadmin.cn');
        return $form;
    }
}